package cn.wen.java.utils;

import java.util.Collection;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;

import cn.wen.java.lang.HelperOfClass;
import cn.wen.java.lang.HelperOfString;

/**
 * Title：断言类
 * <p>
 * Description：Asserts, With logging
 * <p>
 * Copyright：Copyright (c) 2010-2012
 * 
 * @author <a href="mailto:ct_wen@qq.com">ctwen</a>, 温承瑭
 * @version Revision：1.0 Date：2011/1/29 Description：初完成。
 *          <p>
 *          Revision：1.5 Date：2011/1/30 Description：新增isEqual，添加错误日志。
 *          <p>
 *          Revision：1.6 Date：2011/1/30 Description：新增isTrue，isFalse。deprecated isEqual(Boolean)
 *          <p>
 *          Revision：1.7 Date：2011/4/14 Description：新增抛自选异常方法（无日志记录）。
 *          <p>
 */
public abstract class Assert {

    private static Logger log = Logger.getLogger(Assert.class);

    /**
     * Asserts that two objects are equal. If they are not, throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     */
    public static void isEqual(Object expected, Object actual) {
	isEqual(expected, actual,
		HelperOfString.format("[Assertion failed] - the actual:{0}, argument must be expected:{1}", actual, expected));
    }

    /**
     * Asserts that two objects are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value, &quot;The both value must be equal&quot;);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     */
    public static void isEqual(Object expected, Object actual, String message) {
	isNotNull(expected, "[Assertion failed] - the expected argument must be not null");

	if (expected.equals(actual)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new IllegalArgumentException();
	    }
	    else {
		throw new IllegalArgumentException(message);
	    }
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Asserts that two objects are equal. If they are not. throw exception.
     * 
     * <pre class="code">
     * Assert.isEqual(value, value, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @throws Exception if the both value is not <code>equal</code>
     */
    public static void isEqual(Object expected, Object actual, Exception exception) throws Exception {
	isNotNull(expected, "[Assertion failed] - the expected argument must be not null");

	if (expected.equals(actual)) return;

	throw exception;
    }

    /**
     * Asserts that two Strings are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     */
    public static void isEqual(String expected, String actual) {
	isEqual((Object) expected, (Object) actual);
    }

    /**
     * Asserts that two Strings are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value, &quot;The both value must be equal&quot;);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     */
    public static void isEqual(String expected, String actual, String message) {
	isEqual((Object) expected, (Object) actual, message);
    }

    /**
     * Asserts that two Numbers are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     */
    public static void isEqual(Number expected, Number actual) {
	isEqual((Object) expected, (Object) actual);
    }

    /**
     * Asserts that two Numbers are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value, &quot;The both value must be equal&quot;);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     */
    public static void isEqual(Number expected, Number actual, String message) {
	isEqual((Object) expected, (Object) actual, message);
    }

    /**
     * Asserts that two Booleans are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     * @deprecated {@link Assert#isTrue} or {@link Assert#isFalse}
     */
    public static void isEqual(Boolean expected, Boolean actual) {
	isEqual((Object) expected, (Object) actual);
    }

    /**
     * Asserts that two Booleans are equal. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isEqual(value, value, &quot;The both value must be equal&quot;);
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the both value is not <code>equal</code>
     * @deprecated {@link Assert#isTrue} or {@link Assert#isFalse}
     */
    public static void isEqual(Boolean expected, Boolean actual, String message) {
	isEqual((Object) expected, (Object) actual, message);
    }

    /**
     * Asserts that two Booleans are equal. If they are not. throw exception.
     * 
     * <pre class="code">
     * Assert.isEqual(value, value, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param expected the expected
     * @param actual the actual
     * @param exception the throw exception
     * @throws Exception if the both value is not <code>equal</code>
     * @deprecated {@link Assert#isTrue} or {@link Assert#isFalse}
     */
    public static void isEqual(Boolean expected, Boolean actual, Exception exception) throws Exception {
	isEqual((Object) expected, (Object) actual, exception);
    }

    /**
     * Asserts that Booleans is true. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isTrue(value);
     * </pre>
     * 
     * @param actual the actual
     * @throws IllegalArgumentException if the value is <code>false</code>
     */
    public static void isTrue(Boolean actual) {
	isEqual(Boolean.TRUE, actual);
    }

    /**
     * Asserts that Booleans is true. If they are not. throw exception.
     * 
     * <pre class="code">
     * Assert.isTrue(value, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param actual the actual
     * @param exception the throw exception
     * @throws Exception if the both value is not <code>equal</code>
     */
    public static void isTrue(Boolean actual, Exception exception) throws Exception {
	isEqual(Boolean.TRUE, actual, exception);
    }

    /**
     * Asserts that Booleans is true. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isTrue(value, &quot;The value must be true&quot;);
     * </pre>
     * 
     * @param actual the actual
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the value is <code>false</code>
     */
    public static void isTrue(Boolean actual, String message) {
	isEqual(Boolean.TRUE, actual, message);
    }

    /**
     * Asserts that Booleans is false. If they are not. throw exception.
     * 
     * <pre class="code">
     * Assert.isFalse(value, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param actual the actual
     * @param exception the throw exception
     * @throws Exception if the value is <code>true</code>
     */
    public static void isFalse(Boolean actual, Exception exception) throws Exception {
	isEqual(Boolean.FALSE, actual, exception);
    }

    /**
     * Asserts that Booleans is false. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isFalse(value);
     * </pre>
     * 
     * @param actual the actual
     * @throws IllegalArgumentException if the value is <code>true</code>
     */
    public static void isFalse(Boolean actual) {
	isEqual(Boolean.FALSE, actual);
    }

    /**
     * Asserts that Booleans is false. If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isFalse(value, &quot;The value must be false&quot;);
     * </pre>
     * 
     * @param actual the actual
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the value is <code>true</code>
     */
    public static void isFalse(Boolean actual, String message) {
	isEqual(Boolean.FALSE, actual, message);
    }

    /**
     * Assert that an object is not <code>null</code> ,If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isNotNull(value, &quot;The value must be not null&quot;);
     * </pre>
     * 
     * @param object the object to check
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the object is <code>null</code>
     */
    public static void isNotNull(Object object, String message) {
	if (object != null) return;

	try {
	    throw new IllegalArgumentException(message);
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that an object is not <code>null</code> ,If they are not. throw exception.
     * 
     * <pre class="code">
     * Assert.isNotNull(value, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param object the object to check
     * @param exception the throw exception
     * @throws Exception if the object is <code>null</code>
     */
    public static void isNotNull(Object object, Exception exception) throws Exception {
	if (object != null) return;

	throw exception;
    }

    /**
     * Assert that an object is not <code>null</code> ,If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isNotNull(value);
     * </pre>
     * 
     * @param object the object to check
     * @throws IllegalArgumentException if the object is <code>null</code>
     */
    public static void isNotNull(Object object) {
	isNotNull(object, "[Assertion failed] - the object argument must be not null");
    }

    /**
     * Assert that an object is <code>null</code> ,If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isNull(value, &quot;The value must be null&quot;);
     * </pre>
     * 
     * @param object the object to check
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the object is not <code>null</code>
     */
    public static void isNull(Object object, String message) {
	if (object == null) return;

	try {
	    throw new IllegalArgumentException(message);
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that an object is <code>null</code> ,If they are not. throw exception.
     * 
     * <pre class="code">
     * Assert.isNull(value, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param object the object to check
     * @param exception the throw exception
     * @throws Exception if the object is not <code>null</code>
     */
    public static void isNull(Object object, Exception exception) throws Exception {
	if (object == null) return;

	throw exception;
    }

    /**
     * Assert that an object is <code>null</code> ,If they are not. throw exception. With logging
     * 
     * <pre class="code">
     * Assert.isNull(value);
     * </pre>
     * 
     * @param object the object to check
     * @throws IllegalArgumentException if the object is not <code>null</code>
     */
    public static void isNull(Object object) {
	isNull(object, "[Assertion failed] - the object argument must be null");
    }

    /**
     * Assert that the given String has valid text content; that is, it must not be <code>null</code> and must contain at least
     * one non-whitespace character. With logging
     * 
     * <pre class="code">
     * Assert.isEmpty(name, &quot;'name' must not be empty&quot;);
     * </pre>
     * 
     * @param text the String to check
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the object is empty.
     * @see HelperOfString#isEmpty
     */
    public static void isNotEmpty(String text, String message) {
	if (!HelperOfString.isEmpty(text)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new IllegalArgumentException();
	    }
	    else {
		throw new IllegalArgumentException(message);
	    }
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that the given String has valid text content; that is, it must not be <code>null</code> and must contain at least
     * one non-whitespace character.
     * 
     * <pre class="code">
     * Assert.isEmpty(name, new Exception(&quot;...&quot;));
     * </pre>
     * 
     * @param text the String to check
     * @param exception the throw exception
     * @throws Exception if the object is empty.
     * @see HelperOfString#isEmpty
     */
    public static void isNotEmpty(String text, Exception exception) throws Exception {
	if (!HelperOfString.isEmpty(text)) return;

	throw exception;
    }

    /**
     * Assert that the given String has valid text content; that is, it must not be <code>null</code> and must contain at least
     * one non-whitespace character.. With logging
     * 
     * <pre class="code">
     * Assert.isEmpty(name);
     * </pre>
     * 
     * @param text the String to check
     * @throws IllegalArgumentException if the object is empty.
     * @see HelperOfString#isEmpty
     */
    public static void isNotEmpty(String text) {
	isNotEmpty(text, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
    }

    /**
     * Assert that an array has elements; that is, it must not be <code>null</code> and must have at least one element. With
     * logging
     * 
     * <pre class="code">
     * Assert.isEmpty(array, &quot;The array must have elements&quot;);
     * </pre>
     * 
     * @param array the array to check
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
     */
    public static void isNotEmpty(Object[] array, String message) {
	if (!ArrayUtils.isEmpty(array)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new IllegalArgumentException();
	    }
	    else {
		throw new IllegalArgumentException(message);
	    }
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that an array has elements; that is, it must not be <code>null</code> and must have at least one element. With
     * logging
     * 
     * <pre class="code">
     * Assert.isEmpty(array);
     * </pre>
     * 
     * @param array the array to check
     * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
     */
    public static void isNotEmpty(Object[] array) {
	isNotEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
    }

    /**
     * Assert that a collection has elements; that is, it must not be <code>null</code> and must have at least one element. With
     * logging
     * 
     * <pre class="code">
     * Assert.isEmpty(collection, &quot;Collection must have elements&quot;);
     * </pre>
     * 
     * @param collection the collection to check
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
     */
    public static void isNotEmpty(Collection<?> collection, String message) {
	if (!CollectionUtils.isEmpty(collection)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new IllegalArgumentException();
	    }
	    else {
		throw new IllegalArgumentException(message);
	    }
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that a collection has elements; that is, it must not be <code>null</code> and must have at least one element. With
     * logging
     * 
     * <pre class="code">
     * Assert.isEmpty(collection, &quot;Collection must have elements&quot;);
     * </pre>
     * 
     * @param collection the collection to check
     * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
     */
    public static void isNotEmpty(Collection<?> collection) {
	isNotEmpty(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
    }

    /**
     * Assert that a Map has entries; that is, it must not be <code>null</code> and must have at least one entry. With logging
     * 
     * <pre class="code">
     * Assert.isEmpty(map, &quot;Map must have entries&quot;);
     * </pre>
     * 
     * @param map the map to check
     * @param message the exception message to use if the assertion fails
     * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
     */
    public static void isNotEmpty(Map<?, ?> map, String message) {
	if (!MapUtils.isEmpty(map)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new IllegalArgumentException();
	    }
	    else {
		throw new IllegalArgumentException(message);
	    }
	}
	catch (IllegalArgumentException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that a Map has entries; that is, it must not be <code>null</code> and must have at least one entry. With logging
     * 
     * <pre class="code">
     * Assert.isEmpty(map);
     * </pre>
     * 
     * @param map the map to check
     * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
     */
    public static void isNotEmpty(Map<?, ?> map) {
	isNotEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
    }

    /**
     * Assert that the provided object is an instance of the provided class. With logging
     * 
     * <pre class="code">
     * Assert.isInstanceOf(Foo.class, foo);
     * </pre>
     * 
     * @param clazz the required class
     * @param obj the object to check
     * @throws IllegalArgumentException if the object is not an instance of clazz
     * @see Class#isInstance
     */
    public static void isInstanceOf(Class<?> clazz, Object obj) {
	isInstanceOf(clazz, obj, null);
    }

    /**
     * Assert that the provided object is an instance of the provided class. With logging
     * 
     * <pre class="code">
     * Assert.isInstanceOf(Foo.class, foo);
     * </pre>
     * 
     * @param type the type to check against
     * @param obj the object to check
     * @param message a message which will be prepended to the message produced by the function itself, and which may be used to
     *            provide context. It should normally end in a ": " or ". " so that the function generate message looks ok when
     *            prepended to it.
     * @throws ClassCastException if the object is not an instance of clazz
     * @see Class#isInstance
     */
    public static void isInstanceOf(Class<?> type, Object obj, String message) {
	isNotNull(type, "Type to check against must not be null");

	if (HelperOfClass.isInstanceOf(type, obj)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new ClassCastException(HelperOfString.format("Object of class [{0}] must be an instance of {1}",
			(obj != null ? obj.getClass().getName() : "null"), type));
	    }
	    else {
		throw new ClassCastException(HelperOfString.format("{0} Object of class [{1}] must be an instance of {2}",
			message, (obj != null ? obj.getClass().getName() : "null"), type));
	    }
	}
	catch (ClassCastException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

    /**
     * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>. With logging
     * 
     * <pre class="code">
     * Assert.isAssignableFrom(Number.class, myClass);
     * </pre>
     * 
     * @param superType the super type to check
     * @param subType the sub type to check
     * @throws ClassCastException if the classes are not assignable
     */
    public static void isAssignableFrom(Class<?> superType, Class<?> subType) {
	isAssignableFrom(superType, subType, "");
    }

    /**
     * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>. With logging
     * 
     * <pre class="code">
     * Assert.isAssignableFrom(Number.class, myClass);
     * </pre>
     * 
     * @param superType the super type to check against
     * @param subType the sub type to check
     * @param message a message which will be prepended to the message produced by the function itself, and which may be used to
     *            provide context. It should normally end in a ": " or ". " so that the function generate message looks ok when
     *            prepended to it.
     * @throws ClassCastException if the classes are not assignable
     */
    public static void isAssignableFrom(Class<?> superType, Class<?> subType, String message) {
	isNotNull(superType, "Type to check against must not be null");

	if (HelperOfClass.isAssignableFrom(superType, subType)) return;

	try {
	    if (HelperOfString.isEmpty(message)) {
		throw new ClassCastException(HelperOfString.format("{0} is not assignable to {1}", subType, superType));
	    }
	    else {
		throw new ClassCastException(HelperOfString.format("{0} {1} is not assignable to {2}", message, subType,
			superType));
	    }
	}
	catch (ClassCastException e) {
	    log.error(e.getMessage(), e);
	    throw e;
	}
    }

}
